App.require(["math", 'quaternion'], function (_Math, quaternion) {
    var Quaternion = quaternion.Quaternion;

    function Matrix4() {
        this.elements = [
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ];
    }
    Matrix4.prototype = {
        constructor: Matrix4,

        set: function (n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) {
            var te = this.elements;
            te[0] = n11;
            te[4] = n12;
            te[8] = n13;
            te[12] = n14;
            te[1] = n21;
            te[5] = n22;
            te[9] = n23;
            te[13] = n24;
            te[2] = n31;
            te[6] = n32;
            te[10] = n33;
            te[14] = n34;
            te[3] = n41;
            te[7] = n42;
            te[11] = n43;
            te[15] = n44;

            return this;
        },

        identity: function () {
            this.set(
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
            );

            return this;
        },

        clone: function () {
            return Matrix4.fromArray(this.elements);
        },

        copy: function (m) {
            var te = this.elements;
            var me = m.elements;

            te[0] = me[0];
            te[1] = me[1];
            te[2] = me[2];
            te[3] = me[3];
            te[4] = me[4];
            te[5] = me[5];
            te[6] = me[6];
            te[7] = me[7];
            te[8] = me[8];
            te[9] = me[9];
            te[10] = me[10];
            te[11] = me[11];
            te[12] = me[12];
            te[13] = me[13];
            te[14] = me[14];
            te[15] = me[15];

            return this;
        },

        copyPosition: function (m) {
            var te = this.elements,
                me = m.elements;

            te[12] = me[12];
            te[13] = me[13];
            te[14] = me[14];

            return this;
        },

        setPosition: function (v) {
            var te = this.elements;

            te[12] = v.x;
            te[13] = v.y;
            te[14] = v.z;

            return this;
        },

        extractBasic: function (xAxis, yAxis, zAxis) {
            Vector3.fromMatrixColumn(this, 0, xAxis);
            Vector3.fromMatrixColumn(this, 1, yAxis);
            Vector3.fromMatrixColumn(this, 2, zAxis);

            return this;
        },

        multiply: function (m) {
            return Matrix4.multiply(this, m, this);
        },

        premultiply: function (m) {
            return Matrix4.multiply(m, this, this);
        },

        multiplyScalar: function (s) {
            var te = this.elements;

            te[0] *= s;
            te[4] *= s;
            te[8] *= s;
            te[12] *= s;
            te[1] *= s;
            te[5] *= s;
            te[9] *= s;
            te[13] *= s;
            te[2] *= s;
            te[6] *= s;
            te[10] *= s;
            te[14] *= s;
            te[3] *= s;
            te[7] *= s;
            te[11] *= s;
            te[15] *= s;

            return this;
        },

        determinant: function () {
            var te = this.elements;

            var n11 = te[0],
                n12 = te[4],
                n13 = te[8],
                n14 = te[12];
            var n21 = te[1],
                n22 = te[5],
                n23 = te[9],
                n24 = te[13];
            var n31 = te[2],
                n32 = te[6],
                n33 = te[10],
                n34 = te[14];
            var n41 = te[3],
                n42 = te[7],
                n43 = te[11],
                n44 = te[15];
            return (
                n41 * (
                    +n14 * n23 * n32 -
                    n13 * n24 * n32 -
                    n14 * n22 * n33 +
                    n12 * n24 * n33 +
                    n13 * n22 * n34 -
                    n12 * n23 * n34
                ) +
                n42 * (
                    +n11 * n23 * n34 -
                    n11 * n24 * n33 +
                    n14 * n21 * n33 -
                    n13 * n21 * n34 +
                    n13 * n24 * n31 -
                    n14 * n23 * n31
                ) +
                n43 * (
                    +n11 * n24 * n32 -
                    n11 * n22 * n34 -
                    n14 * n21 * n32 +
                    n12 * n21 * n34 +
                    n14 * n22 * n31 -
                    n12 * n24 * n31
                ) +
                n44 * (
                    -n13 * n22 * n31 -
                    n11 * n23 * n32 +
                    n11 * n22 * n33 +
                    n13 * n21 * n32 -
                    n12 * n21 * n33 +
                    n12 * n23 * n31
                )

            );
        },

        scale: function (v) {
            var te = this.elements;
            var x, y, z;
            if (arguments.length == 1) {
                x = v.x, y = v.y, z = v.z;
            } else {
                x = arguments[0], y = arguments[1], z = arguments[2];
            }

            te[0] *= x;
            te[4] *= y;
            te[8] *= z;
            te[1] *= x;
            te[5] *= y;
            te[9] *= z;
            te[2] *= x;
            te[6] *= y;
            te[10] *= z;
            te[3] *= x;
            te[7] *= y;
            te[11] *= z;

            return this;
        },

        rotateX: function (angle) {
            Matrix4.matrix = Matrix4.matrix || new Matrix4();
            this.multiply(Matrix4.fromRotationX(angle, Matrix4.matrix));
            return this;
        },

        rotateY: function (angle) {
            Matrix4.matrix = Matrix4.matrix || new Matrix4();
            this.multiply(Matrix4.fromRotationY(angle, Matrix4.matrix));
            return this;
        },

        rotateZ: function (angle) {
            Matrix4.matrix = Matrix4.matrix || new Matrix4();
            this.multiply(Matrix4.fromRotationZ(angle, Matrix4.matrix));
            return this;
        },

        rotate: function (axis, angle) {
            Matrix4.matrix = Matrix4.matrix || new Matrix4();
            this.multiply(Matrix4.fromRotationAxis(axis, angle, Matrix4.matrix));
            return this;
        },

        translate: function (x, y, z) {
            var e = this.elements;
            e[12] += e[0] * x + e[4] * y + e[8] * z;
            e[13] += e[1] * x + e[5] * y + e[9] * z;
            e[14] += e[2] * x + e[6] * y + e[10] * z;
            e[15] += e[3] * x + e[7] * y + e[11] * z;
            return this;
        },

        getMaxScaleOnAxis: function () {
            var te = this.elements;

            var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
            var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
            var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];

            return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));
        },

        decompose: function (position, quaternion, scale) {
            var vector = Matrix4.vector = Matrix4.vector || new Vector3();
            var matrix = Matrix4.matrix = Matrix4.matrix || new Matrix4();

            var te = this.elements;

            var sx = vector.set(te[0], te[1], te[2]).length();
            var sy = vector.set(te[4], te[5], te[6]).length();
            var sz = vector.set(te[8], te[9], te[10]).length();

            var det = this.determinant();
            if (det < 0) sx = -sx;

            position.x = te[12];
            position.y = te[13];
            position.z = te[14];

            matrix.copy(this);

            var invSX = 1 / sx;
            var invSY = 1 / sy;
            var invSZ = 1 / sz;

            matrix.elements[0] *= invSX;
            matrix.elements[1] *= invSX;
            matrix.elements[2] *= invSX;

            matrix.elements[4] *= invSY;
            matrix.elements[5] *= invSY;
            matrix.elements[6] *= invSY;

            matrix.elements[8] *= invSZ;
            matrix.elements[9] *= invSZ;
            matrix.elements[10] *= invSZ;

            Quaternion.fromRotationMatrix(matrix, quaternion);

            scale.x = sx;
            scale.y = sy;
            scale.z = sz;

            return this;
        },

        equals: function (matrix) {
            var te = this.elements;
            var me = matrix.elements;

            for (var i = 0; i < 16; i++) {
                if (te[i] !== me[i]) return false;
            }

            return true;
        },

        toArray: function (array, offset) {
            if (array === undefined) array = [];
            if (offset === undefined) offset = 0;

            var te = this.elements;

            array[offset] = te[0];
            array[offset + 1] = te[1];
            array[offset + 2] = te[2];
            array[offset + 3] = te[3];

            array[offset + 4] = te[4];
            array[offset + 5] = te[5];
            array[offset + 6] = te[6];
            array[offset + 7] = te[7];

            array[offset + 8] = te[8];
            array[offset + 9] = te[9];
            array[offset + 10] = te[10];
            array[offset + 11] = te[11];

            array[offset + 12] = te[12];
            array[offset + 13] = te[13];
            array[offset + 14] = te[14];
            array[offset + 15] = te[15];

            return array;
        },

        shadowMatrixFromLight: function (m, light) {
            this.translate(light.x, light.y, light.z)
                .multiply(m)
                .translate(-light.x, -light.y, -light.z);

            return this;
        },
        lookAt: function (eye, at, up) {
            Matrix4.matrix = Matrix4.matrix || new Matrix4();
            Matrix4.lookAt(eye, at, up, Matrix4.matrix);
            return this.multiply(Matrix4.matrix);
        }
    };

    Matrix4.copyFrom = function (matrix, c) {
        c = c || new Matrix4();
        var te = c.elements,
            me = matrix.elements;
        te[0] = me[0];
        te[1] = me[1];
        te[2] = me[2];
        te[3] = me[3];
        te[4] = me[4];
        te[5] = me[5];
        te[6] = me[6];
        te[7] = me[7];
        te[8] = me[8];
        te[9] = me[9];
        te[10] = me[10];
        te[11] = me[11];
        te[12] = me[12];
        te[13] = me[13];
        te[14] = me[14];
        te[15] = me[15];

        return c;
    };

    Matrix4.fromBasis = function (xAxis, yAxis, zAxis, c) {
        c = c || new Matrix4();
        c.set(
            xAxis.x, yAxis.x, zAxis.x, 0,
            xAxis.y, yAxis.y, zAxis.y, 0,
            xAxis.z, yAxis.z, zAxis.z, 0,
            0, 0, 0, 1
        );
        return c;
    };

    Matrix4.extractRotation = function (m, c) {
        c = c || new Matrix4();
        var v1 = Matrix4.vector = Matrix4.vector || new Vector3();

        var te = c.elements;
        var me = m.elements;

        var scaleX = 1 / Vector3.fromMatrixColumn(m, 0, v1).length();
        var scaleY = 1 / Vector3.fromMatrixColumn(m, 1, v1).length();
        var scaleZ = 1 / Vector3.fromMatrixColumn(m, 2, v1).length();

        te[0] = me[0] * scaleX;
        te[1] = me[1] * scaleX;
        te[2] = me[2] * scaleX;

        te[4] = me[4] * scaleY;
        te[5] = me[5] * scaleY;
        te[6] = me[6] * scaleY;

        te[8] = me[8] * scaleZ;
        te[9] = me[9] * scaleZ;
        te[10] = me[10] * scaleZ;

        return c;
    };

    Matrix4.fromEuler = function (euler, c) {
        c = c || new Matrix4();
        var te = c.elements;
        var x = euler.x,
            y = euler.y,
            z = euler.z;
        var a = Math.cos(x),
            b = Math.sin(x);
        var c = Math.cos(y),
            d = Math.sin(y);
        var e = Math.cos(z),
            f = Math.sin(z);

        if (euler.order === 'XYZ') {
            var ae = a * e,
                af = a * f,
                be = b * e,
                bf = b * f;

            te[0] = c * e;
            te[4] = -c * f;
            te[8] = d;

            te[1] = af + be * d;
            te[5] = ae - bf * d;
            te[9] = -b * c;

            te[2] = bf - ae * d;
            te[6] = be + af * d;
            te[10] = a * c;
        } else if (euler.order === 'YXZ') {
            var ce = c * e,
                cf = c * f,
                de = d * e,
                df = d * f;

            te[0] = ce + df * b;
            te[4] = de * b - cf;
            te[8] = a * d;

            te[1] = a * f;
            te[5] = a * e;
            te[9] = -b;

            te[2] = cf * b - de;
            te[6] = df + ce * b;
            te[10] = a * c;
        } else if (euler.order === 'ZXY') {
            var ce = c * e,
                cf = c * f,
                de = d * e,
                df = d * f;

            te[0] = ce - df * b;
            te[4] = -a * f;
            te[8] = de + cf * b;

            te[1] = cf + de * b;
            te[5] = a * e;
            te[9] = df - ce * b;

            te[2] = -a * d;
            te[6] = b;
            te[10] = a * c;
        } else if (euler.order === 'ZYX') {
            var ae = a * e,
                af = a * f,
                be = b * e,
                bf = b * f;

            te[0] = c * e;
            te[4] = be * d - af;
            te[8] = ae * d + bf;

            te[1] = c * f;
            te[5] = bf * d + ae;
            te[9] = af * d - be;

            te[2] = -d;
            te[6] = b * c;
            te[10] = a * c;
        } else if (euler.order === 'YZX') {
            var ac = a * c,
                ad = a * d,
                bc = b * c,
                bd = b * d;

            te[0] = c * e;
            te[4] = bd - ac * f;
            te[8] = bc * f + ad;

            te[1] = f;
            te[5] = a * e;
            te[9] = -b * e;

            te[2] = -d * e;
            te[6] = ad * f + bc;
            te[10] = ac - bd * f;
        } else if (euler.order === 'XZY') {
            var ac = a * c,
                ad = a * d,
                bc = b * c,
                bd = b * d;

            te[0] = c * e;
            te[4] = -f;
            te[8] = d * e;

            te[1] = ac * f + bd;
            te[5] = a * e;
            te[9] = ad * f - bc;

            te[2] = bc * f - ad;
            te[6] = b * e;
            te[10] = bd * f + ac;
        }

        te[3] = 0;
        te[7] = 0;
        te[11] = 0;

        te[12] = 0;
        te[13] = 0;
        te[14] = 0;
        te[15] = 1;

        return c;
    };

    Matrix4.fromQuaternion = function (q, c) {
        c = c || new Matrix4();
        var te = c.elements;

        var x = q.x,
            y = q.y,
            z = q.z,
            w = q.w;
        var x2 = x + x,
            y2 = y + y,
            z2 = z + z;
        var xx = x * x2,
            xy = x * y2,
            xz = x * z2;
        var yy = y * y2,
            yz = y * z2,
            zz = z * z2;
        var wx = w * x2,
            wy = w * y2,
            wz = w * z2;

        te[0] = 1 - (yy + zz);
        te[4] = xy - wz;
        te[8] = xz + wy;

        te[1] = xy + wz;
        te[5] = 1 - (xx + zz);
        te[9] = yz - wx;

        te[2] = xz - wy;
        te[6] = yz + wx;
        te[10] = 1 - (xx + yy);

        te[3] = 0;
        te[7] = 0;
        te[11] = 0;

        te[12] = 0;
        te[13] = 0;
        te[14] = 0;
        te[15] = 1;

        return c;
    };

    Matrix4.lookAt = function (eye, center, up, c) {
        c = c || new Matrix4();
        var x0, x1, x2, y0, y1, y2, z0, z1, z2, len;
        var eyex = eye.x;
        var eyey = eye.y;
        var eyez = eye.z;
        var upx = up.x;
        var upy = up.y;
        var upz = up.z;
        var centerx = center.x;
        var centery = center.y;
        var centerz = center.z;
        var out = c.elements;

        if (Math.abs(eyex - centerx) < 0.00001 &&
            Math.abs(eyey - centery) < 0.00001 &&
            Math.abs(eyez - centerz) < 0.00001) {
            return c.identity();
        }

        z0 = eyex - centerx;
        z1 = eyey - centery;
        z2 = eyez - centerz;

        len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
        z0 *= len;
        z1 *= len;
        z2 *= len;

        x0 = upy * z2 - upz * z1;
        x1 = upz * z0 - upx * z2;
        x2 = upx * z1 - upy * z0;
        len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
        if (!len) {
            x0 = 0;
            x1 = 0;
            x2 = 0;
        } else {
            len = 1 / len;
            x0 *= len;
            x1 *= len;
            x2 *= len;
        }

        y0 = z1 * x2 - z2 * x1;
        y1 = z2 * x0 - z0 * x2;
        y2 = z0 * x1 - z1 * x0;

        len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
        if (!len) {
            y0 = 0;
            y1 = 0;
            y2 = 0;
        } else {
            len = 1 / len;
            y0 *= len;
            y1 *= len;
            y2 *= len;
        }

        out[0] = x0;
        out[1] = y0;
        out[2] = z0;
        out[3] = 0;
        out[4] = x1;
        out[5] = y1;
        out[6] = z1;
        out[7] = 0;
        out[8] = x2;
        out[9] = y2;
        out[10] = z2;
        out[11] = 0;
        out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
        out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
        out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
        out[15] = 1;

        return c;
    };

    Matrix4.targetTo = function (eye, target, up, c) {
        c = c || new Matrix4();
        var eyex = eye.x,
            eyey = eye.y,
            eyez = eye.z,
            upx = up.x,
            upy = up.y,
            upz = up.z;

        var z0 = eyex - target.x,
            z1 = eyey - target.y,
            z2 = eyez - target.z;

        var len = z0 * z0 + z1 * z1 + z2 * z2;

        if (len > 0) {
            len = 1 / Math.sqrt(len);
            z0 *= len;
            z1 *= len;
            z2 *= len;
        }

        var x0 = upy * z2 - upz * z1,
            x1 = upz * z0 - upx * z2,
            x2 = upx * z1 - upy * z0;
        var out = c.elements;

        out[0] = x0;
        out[1] = x1;
        out[2] = x2;
        out[3] = 0;
        out[4] = z1 * x2 - z2 * x1;
        out[5] = z2 * x0 - z0 * x2;
        out[6] = z0 * x1 - z1 * x0;
        out[7] = 0;
        out[8] = z0;
        out[9] = z1;
        out[10] = z2;
        out[11] = 0;
        out[12] = eyex;
        out[13] = eyey;
        out[14] = eyez;
        out[15] = 1;

        return c;
    };

    Matrix4.multiply = function (a, b, c) {
        c = c || new Matrix4();
        var ae = a.elements;
        var be = b.elements;
        var te = c.elements;

        var a11 = ae[0],
            a12 = ae[4],
            a13 = ae[8],
            a14 = ae[12];
        var a21 = ae[1],
            a22 = ae[5],
            a23 = ae[9],
            a24 = ae[13];
        var a31 = ae[2],
            a32 = ae[6],
            a33 = ae[10],
            a34 = ae[14];
        var a41 = ae[3],
            a42 = ae[7],
            a43 = ae[11],
            a44 = ae[15];

        var b11 = be[0],
            b12 = be[4],
            b13 = be[8],
            b14 = be[12];
        var b21 = be[1],
            b22 = be[5],
            b23 = be[9],
            b24 = be[13];
        var b31 = be[2],
            b32 = be[6],
            b33 = be[10],
            b34 = be[14];
        var b41 = be[3],
            b42 = be[7],
            b43 = be[11],
            b44 = be[15];

        te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
        te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
        te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
        te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;

        te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
        te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
        te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
        te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;

        te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
        te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
        te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
        te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;

        te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
        te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
        te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
        te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;

        return c;
    };

    Matrix4.transpose = function (m, c) {
        c = c || new Matrix4();
        var a = m.elements;
        var out = c.elements;
        if (out === a) {
            var a01 = a[1],
                a02 = a[2],
                a03 = a[3];
            var a12 = a[6],
                a13 = a[7];
            var a23 = a[11];

            out[1] = a[4];
            out[2] = a[8];
            out[3] = a[12];
            out[4] = a01;
            out[6] = a[9];
            out[7] = a[13];
            out[8] = a02;
            out[9] = a12;
            out[11] = a[14];
            out[12] = a03;
            out[13] = a13;
            out[14] = a23;
        } else {
            out[0] = a[0];
            out[1] = a[4];
            out[2] = a[8];
            out[3] = a[12];
            out[4] = a[1];
            out[5] = a[5];
            out[6] = a[9];
            out[7] = a[13];
            out[8] = a[2];
            out[9] = a[6];
            out[10] = a[10];
            out[11] = a[14];
            out[12] = a[3];
            out[13] = a[7];
            out[14] = a[11];
            out[15] = a[15];
        }

        return c;
    };

    Matrix4.fromInverse = function (m, c) {
        c = c || new Matrix4();
        var te = c.elements,
            me = m.elements,

            n11 = me[0],
            n21 = me[1],
            n31 = me[2],
            n41 = me[3],
            n12 = me[4],
            n22 = me[5],
            n32 = me[6],
            n42 = me[7],
            n13 = me[8],
            n23 = me[9],
            n33 = me[10],
            n43 = me[11],
            n14 = me[12],
            n24 = me[13],
            n34 = me[14],
            n44 = me[15],

            t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
            t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
            t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
            t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;

        var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;

        if (det === 0) {
            return c.identity();
        }

        var detInv = 1 / det;

        te[0] = t11 * detInv;
        te[1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * detInv;
        te[2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * detInv;
        te[3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * detInv;

        te[4] = t12 * detInv;
        te[5] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * detInv;
        te[6] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * detInv;
        te[7] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * detInv;

        te[8] = t13 * detInv;
        te[9] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * detInv;
        te[10] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * detInv;
        te[11] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * detInv;

        te[12] = t14 * detInv;
        te[13] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * detInv;
        te[14] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * detInv;
        te[15] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * detInv;

        return c;
    };

    Matrix4.fromTranslation = function (x, y, z, c) {
        c = c || new Matrix4();
        c.set(
            1, 0, 0, x,
            0, 1, 0, y,
            0, 0, 1, z,
            0, 0, 0, 1
        );

        return c;
    };

    Matrix4.fromRotationX = function (theta, m) {
        m = m || new Matrix4();
        var c = Math.cos(theta),
            s = Math.sin(theta);

        m.set(
            1, 0, 0, 0,
            0, c, -s, 0,
            0, s, c, 0,
            0, 0, 0, 1
        );

        return m;
    };

    Matrix4.fromRotationY = function (theta, m) {
        m = m || new Matrix4();
        var c = Math.cos(theta),
            s = Math.sin(theta);

        m.set(
            c, 0, s, 0,
            0, 1, 0, 0,
            -s, 0, c, 0,
            0, 0, 0, 1
        );

        return m;
    };

    Matrix4.fromRotationZ = function (theta, m) {
        m = m || new Matrix4();
        var c = Math.cos(theta),
            s = Math.sin(theta);

        m.set(
            c, -s, 0, 0,
            s, c, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        );

        return m;
    };

    Matrix4.fromRotationAxis = function (axis, angle, m) {
        var q = Matrix4.quaternion = Matrix4.quaternion || new Quaternion();
        Quaternion.fromAxisAngle(axis, angle, q);

        return Matrix4.fromQuaternion(q, m);
    };

    Matrix4.fromScaling = function (x, y, z, m) {
        m = m || new Matrix4();
        m.set(
            x, 0, 0, 0,
            0, y, 0, 0,
            0, 0, z, 0,
            0, 0, 0, 1
        );

        return m;
    };

    Matrix4.fromCompose = function (position, quaternion, scale, m) {
        m = m || new Matrix4();
        Matrix4.fromQuaternion(quaternion, m);
        m.scale(scale);
        m.setPosition(position);
        return m;
    };

    Matrix4.fromRotateTranslation = function (q, v, m) {
        m = m || new Matrix4();
        var x = q.x,
            y = q.y,
            z = q.z,
            w = q.w;
        var x2 = x + x;
        var y2 = y + y;
        var z2 = z + z;
        var xx = x * x2;
        var xy = x * y2;
        var xz = x * z2;
        var yy = y * y2;
        var yz = y * z2;
        var zz = z * z2;
        var wx = w * x2;
        var wy = w * y2;
        var wz = w * z2;
        var out = m.elements;

        out[0] = 1 - (yy + zz);
        out[1] = xy + wz;
        out[2] = xz - wy;
        out[3] = 0;
        out[4] = xy - wz;
        out[5] = 1 - (xx + zz);
        out[6] = yz + wx;
        out[7] = 0;
        out[8] = xz + wy;
        out[9] = yz - wx;
        out[10] = 1 - (xx + yy);
        out[11] = 0;
        out[12] = v.x;
        out[13] = v.y;
        out[14] = v.z;
        out[15] = 1;

        return m;
    };

    Matrix4.fromRotateTranslateScaling = function (q, v, s, m) {
        m = m || new Matrix4();

        var x = q.x,
            y = q.y,
            z = q.z,
            w = q.w;
        var x2 = x + x;
        var y2 = y + y;
        var z2 = z + z;
        var xx = x * x2;
        var xy = x * y2;
        var xz = x * z2;
        var yy = y * y2;
        var yz = y * z2;
        var zz = z * z2;
        var wx = w * x2;
        var wy = w * y2;
        var wz = w * z2;

        var sx = s.x,
            sy = s.y,
            sz = s.z,
            vx = v.x,
            vy = v.y,
            vz = v.z;

        var out = m.elements;

        out[0] = (1 - (yy + zz)) * sx;
        out[1] = (xy + wz) * sx;
        out[2] = (xz - wy) * sx;
        out[3] = 0;
        out[4] = (xy - wz) * sy;
        out[5] = (1 - (xx + zz)) * sy;
        out[6] = (yz + wx) * sy;
        out[7] = 0;
        out[8] = (xz + wy) * sz;
        out[9] = (yz - wx) * sz;
        out[10] = (1 - (xx + yy)) * sz;
        out[11] = 0;
        out[12] = v.x;
        out[13] = v.y;
        out[14] = v.z;
        out[15] = 1;

        return m;
    };

    Matrix4.fromRotateTranslateScaleOrigin = function (q, v, s, o, m) {
        m = m || new Matrix4();

        var x = q.x,
            y = q.y,
            z = q.z,
            w = q.w;
        var x2 = x + x;
        var y2 = y + y;
        var z2 = z + z;
        var xx = x * x2;
        var xy = x * y2;
        var xz = x * z2;
        var yy = y * y2;
        var yz = y * z2;
        var zz = z * z2;
        var wx = w * x2;
        var wy = w * y2;
        var wz = w * z2;

        var sx = s.x,
            sy = s.y,
            sz = s.z,
            ox = o.x,
            oy = o.y,
            oz = o.z,
            vx = v.x,
            vy = v.y,
            vz = v.z;

        var out = m.elements;

        out[0] = (1 - (yy + zz)) * sx;
        out[1] = (xy + wz) * sx;
        out[2] = (xz - wy) * sx;
        out[3] = 0;
        out[4] = (xy - wz) * sy;
        out[5] = (1 - (xx + zz)) * sy;
        out[6] = (yz + wx) * sy;
        out[7] = 0;
        out[8] = (xz + wy) * sz;
        out[9] = (yz - wx) * sz;
        out[10] = (1 - (xx + yy)) * sz;
        out[11] = 0;
        out[12] = vx + ox - (out[0] * ox + out[4] * oy + out[8] * oz);
        out[13] = vy + oy - (out[1] * ox + out[5] * oy + out[9] * oz);
        out[14] = vz + oz - (out[2] * ox + out[6] * oy + out[10] * oz);
        out[15] = 1;

        return m;
    };

    Matrix4.fromPerspective = function (fov, aspect, near, far, m) {
        m = m || new Matrix4();
        var out = m.elements;
        var f = 1.0 / Math.tan(fov / 2);
        var nf = 1 / (near - far);

        out[0] = f / aspect;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = 0;
        out[5] = f;
        out[6] = 0;
        out[7] = 0;
        out[8] = 0;
        out[9] = 0;
        out[10] = (far + near) * nf;
        out[11] = -1;
        out[12] = 0;
        out[13] = 0;
        out[14] = (2 * far * near) * nf;
        out[15] = 0;

        return m;
    };

    Matrix4.fromFrustum = function (left, right, top, bottom, near, far, m) {
        m = m || new Matrix4();

        var te = m.elements;
        var x = 2 * near / (right - left);
        var y = 2 * near / (top - bottom);

        var a = (right + left) / (right - left);
        var b = (top + bottom) / (top - bottom);
        var c = -(far + near) / (far - near);
        var d = -2 * far * near / (far - near);

        te[0] = x;
        te[4] = 0;
        te[8] = a;
        te[12] = 0;
        te[1] = 0;
        te[5] = y;
        te[9] = b;
        te[13] = 0;
        te[2] = 0;
        te[6] = 0;
        te[10] = c;
        te[14] = d;
        te[3] = 0;
        te[7] = 0;
        te[11] = -1;
        te[15] = 0;

        return m;
    };

    Matrix4.fromLight = function (light, m) {
        m = m || new Matrix4();
        var te = m.elements;
        te[15] = 0;
        te[7] = -1 / light.y;
        return m;
    };

    Matrix4.Ortho = function (left, right, bottom, top, near, far, m) {
        m = m || new Matrix4();

        var te = m.elements;
        var w = 1.0 / (right - left);
        var h = 1.0 / (top - bottom);
        var p = 1.0 / (far - near);

        var x = (right + left) * w;
        var y = (top + bottom) * h;
        var z = (far + near) * p;

        te[0] = 2 * w;
        te[4] = 0;
        te[8] = 0;
        te[12] = -x;
        te[1] = 0;
        te[5] = 2 * h;
        te[9] = 0;
        te[13] = -y;
        te[2] = 0;
        te[6] = 0;
        te[10] = -2 * p;
        te[14] = -z;
        te[3] = 0;
        te[7] = 0;
        te[11] = 0;
        te[15] = 1;

        return m;
    };
    Matrix4.fromOrtho = Matrix4.Ortho;

    Matrix4.fromScaleAlongAxis = function (axis, k, m) {
        m = m || new Matrix4();

        var out = m.elements;
        var a = k - 1;
        var ax = a * axis.x;
        var ay = a * axis.y;
        var az = a * axis.z;

        out[0] = ax * axis.x + 1;
        out[5] = ay * axis.y + 1;
        out[10] = az * axis.z + 1;

        out[1] = out[4] = ax * axis.y;
        out[2] = out[8] = ax * axis.z;
        out[6] = out[9] = ay * axis.z;

        out[3] = out[7] = out[11] = out[12] = out[13] = out[14] = 0;
        out[15] = 1;

        return m;
    };

    Matrix4.fromArray = function (array, offset, m) {
        m = m || new Matrix4();
        if (offset === undefined) offset = 0;

        for (var i = 0; i < 16; i++) {
            m.elements[i] = array[i + offset];
        }

        return m;
    };

    App.define("matrix4", {
        Matrix4: Matrix4
    })
})